{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "(vis-intro)=\n",
    "# Intro to Data Visualisation\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "Here you'll see how to make plots that present data in an engaging and informative way.\n",
    "\n",
    "There are a plethora of options (and packages) for data visualisation using code. First, though a note about the different *philosophies* of data visualisation. There are broadly two categories of approach to using code to create data visualisations: *imperative*, where you build what you want, and *declarative*, where you say what you want. Choosing which to use involves a trade-off: imperative libraries offer you flexibility but at the cost of some verbosity; declarative libraries offer you a quick way to plot your data, but only if it's in the right format to begin with, and customisation may be more difficult.\n",
    "\n",
    "There are also different purposes of data visualisation. It can be useful to bear in mind the three broad categories of visualisation that are out there:\n",
    "\n",
    "- exploratory\n",
    "\n",
    "- scientific\n",
    "\n",
    "- narrative\n",
    "\n",
    "Python excels at exploratory and scientific visualisation. The tools for narrative visualisation are not as good as they could be for making common chart types efficiently, but the endless customisability of one particular Python package (**matplotlib**) means you can always get the effect you need (with some work).\n",
    "\n",
    "### Exploratory Visualisation\n",
    "\n",
    "The first of the three kinds of vis, *exploratory visualisation*, is the kind that you do when you're looking and data and trying to understand it. Just plotting the data is a really good strategy for getting a feel for any issues there might be. This is perhaps most famously demonstrated by *Anscombe's quartet*: four different datasets with the same mean, standard deviation, and correlation but *very* different data distributions.\n",
    "\n",
    "(First let's import the packages we'll need:)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Set seed for reproducibility\n",
    "np.random.seed(10)\n",
    "# Set max rows displayed for readability\n",
    "pd.set_option(\"display.max_rows\", 6)\n",
    "# Plot settings\n",
    "plt.style.use(\n",
    "    \"https://github.com/aeturrell/coding-for-economists/raw/main/plot_style.txt\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Anscombe's quartet*:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "x = [10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5]\n",
    "y1 = [8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 7.24, 4.26, 10.84, 4.82, 5.68]\n",
    "y2 = [9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 6.13, 3.10, 9.13, 7.26, 4.74]\n",
    "y3 = [7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73]\n",
    "x4 = [8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8]\n",
    "y4 = [6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 5.25, 12.50, 5.56, 7.91, 6.89]\n",
    "\n",
    "datasets = {\"I\": (x, y1), \"II\": (x, y2), \"III\": (x, y3), \"IV\": (x4, y4)}\n",
    "\n",
    "fig, axs = plt.subplots(\n",
    "    2,\n",
    "    2,\n",
    "    sharex=True,\n",
    "    sharey=True,\n",
    "    figsize=(4, 4),\n",
    "    gridspec_kw={\"wspace\": 0.08, \"hspace\": 0.08},\n",
    ")\n",
    "axs[0, 0].set(xlim=(0, 20), ylim=(2, 14))\n",
    "axs[0, 0].set(xticks=(0, 10, 20), yticks=(4, 8, 12))\n",
    "\n",
    "for ax, (label, (x, y)) in zip(axs.flat, datasets.items()):\n",
    "    ax.text(0.1, 0.9, label, fontsize=20, transform=ax.transAxes, va=\"top\")\n",
    "    ax.tick_params(direction=\"in\", top=True, right=True)\n",
    "    ax.plot(x, y, \"o\")\n",
    "\n",
    "    # linear regression\n",
    "    p1, p0 = np.polyfit(x, y, deg=1)  # slope, intercept\n",
    "    ax.axline(xy1=(0, p0), slope=p1, color=\"r\", lw=2)\n",
    "\n",
    "    # add text box for the statistics\n",
    "    stats = (\n",
    "        f\"$\\\\mu$ = {np.mean(y):.2f}\\n\"\n",
    "        f\"$\\\\sigma$ = {np.std(y):.2f}\\n\"\n",
    "        f\"$r$ = {np.corrcoef(x, y)[0][1]:.2f}\"\n",
    "    )\n",
    "    bbox = dict(boxstyle=\"round\", fc=\"blanchedalmond\", ec=\"orange\", alpha=0.5)\n",
    "    ax.text(\n",
    "        0.95,\n",
    "        0.07,\n",
    "        stats,\n",
    "        fontsize=9,\n",
    "        bbox=bbox,\n",
    "        transform=ax.transAxes,\n",
    "        horizontalalignment=\"right\",\n",
    "    )\n",
    "\n",
    "plt.suptitle(\"Anscombe's Quartet\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Exploratory visualisation is usually quick and dirty, and flexible too. Some exploratory data viz can be automated, and some of the packages we saw in the chapter on {ref}`exploratory-data-analysis` can do this. For an EDA package that's explicitly built with visalisation in mind, check out [SweetViz](https://github.com/fbdesignpro/sweetviz). Beyond you and perhaps your co-authors/collaborators, not many other people should be seeing your exploratory visualisation.\n",
    "\n",
    "### Scientific Visualisation\n",
    "\n",
    "The second kind, scientific visualisation, is the prime cut of your exploratory visualisation. It's the kind of plot you might include in a more technical paper, the picture that says a thousand words. I often think of the first image of a black hole {cite}`akiyama2019first` as a prime example of this. You can get away with having a high density of information in a scientific plot and, in short format journals, you may need to. The journal *Physical Review Letters*, which has an 8 page limit, has a classic of this genre in more or less every issue. Ensuring that important values can be accurately read from the plot is especially important in these kinds of charts. But they can also be the kind of plot that presents the killer results in a study; they might not be exciting to people who don't look at charts for a living, but they might be exciting and, just as importantly, understandable by your peers.\n",
    "\n",
    "### Narrative Visualisation\n",
    "\n",
    "The third and final kind is narrative visualisation. This is the one that requires the most thought in the step where you go from the first view to the end product. It's a visualisation that doesn't just show a picture, but gives an insight. These are the kind of visualisations that you might see in the *Financial Times*, *The Economist*, or on the BBC News website. They come with aids that help the viewer focus on the aspects that the creator wanted them to (you can think of these aids or focuses as doing for visualisation what bold font does for text). They're well worth using in your work, especially if you're trying to communicate a particular narrative, and especially if the people you're communicating with don't have deep knowledge of the topic. You might use them in a paper that you hope will have a wide readership, in a blog post summarising your work, or in a report intended for a policymaker.\n",
    "\n",
    "You can find more information on the topic in the {ref}`vis-narrative` chapter."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quick guide to data visualisation\n",
    "\n",
    "Addressing data visualisation, a huge topic in itself, is definitely out of scope for this book! But it's worth discussing a few general pointers at the outset that will serve you very well if you follow them.\n",
    "\n",
    "A picture may tell a 1000 words, but you've got to be a bit careful about what those words are. The first question you should ask yourself when it comes to data visualisation is 'what does this plot tell the viewer?', ie what do you want people to take away from your chart. That nugget of information should be as apparent as possible from the plot. Then you want to ensure that people *do* take away what you meant them to; the viewer should be left in little doubt about what you are saying.\n",
    "\n",
    "Another factor to bear in mind is that papers typically don't have more than, say, ten plots in them--and frequently fewer than that. So each one must count and advance the narrative of your work somehow. (Easier to say, hard to do in practice.) As an example, if you have data that are normally distributed, and you want to show this, it's probably not worth showing it on a plot. But if you had two distributions whose differences were important for the overall story you were telling, that might be something to highlight.\n",
    "\n",
    "Then there are more practical matters: is this plot better done as a scatter plot or a line? Should I stack my bar chart or split out the contributions? Those questions address the *type* of plot you're creating. For example, if you have observations that are independent from one another, with no auto-correlation along the x-axis, a scatter plot is more appropriate than a line chart. However, for time series, which tend to exhibit a high degree of auto-correlation, a line chart could be just the thing. As well as the overall type, for example scatter plot, you can think about adding more information through the use of colours, shapes, sizes, and so on. But my advice is always to be sparing with extra dimensions of information as it very quickly becomes difficult to read. In most cases, an x-axis, a y-axis, and, usually, one other dimension (eg colour) will be the best option.\n",
    "\n",
    "Once you've decided on the *type* of chart, you can then think about smaller details. Unfortunately, lack of labels is endemic in economics (\"percent of what!?\", I cry at least three times a day). Always make what you're plotting clear and, if it has units, express them (eg \"Salary (2015 USD)\"). Think carefully about the tick labels to use too; you'll want something quite different for linear versus log plots. Titles can be helpful too, if the axes labels and the chart by themselves don't tell the whole story. \n",
    "\n",
    "Then, if there are very specific features you'd like to draw attention to, you can achieve this with text labels (as used liberally in the data visualisations you'll see in newspapers like the *Financial Times*), greying out all but the most interesting data point, etc.; anything that singles out one part of the chart as the interesting one. A common trick is to plot the less important features with greater transparency and the important line/point/bar with solid colour. These all have the effect of drawing the eye straight to where it should spend the most time.\n",
    "\n",
    "This is just the briefest of brief overviews of what you should bear in mind for good visualisation; I highly recommend the short and delightful [Fundamentals of Data visualisation](https://clauswilke.com/dataviz/) if you'd like to know more.\n",
    "\n",
    "In terms of further resources to help you choose the right plot for the occassion, you can't go too far wrong than the [*Financial Times* Visual Vocabulary of charts](https://github.com/ft-interactive/chart-doctor/tree/master/visual-vocabulary). And, please, please use [vector graphics](https://clauswilke.com/dataviz/image-file-formats.html) whenever you can!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Colour\n",
    "\n",
    "This section has benefitted from this [blog piece](https://blog.datawrapper.de/which-color-scale-to-use-in-data-vis/) on visualisation and colour, and you can find more information there.\n",
    "\n",
    "Colours often make a chart come alive, but, when encoding differences with colour, think carefully about what would serve your audience and message best. It's best not to use colour randomly, but to choose colours that either add information to the chart or get out of the way of the message. Often, you'll want to draw your colours from a 'colour palette', a collection of different colours that work together to create a particular effect. The best colour palettes take into account that colour blindness is a problem for many people, and they remain informative even when converted to greyscale.\n",
    "\n",
    "One of the most popular Python visualisation libraries, **matplotlib**, comes with a wide range of colour palettes available [here](https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html) and you can find another good package for colour palettes [here](https://colorcet.holoviz.org/).\n",
    "\n",
    "#### Categorical Data\n",
    "\n",
    "For (unordered) categorical data, visually distinct colour palettes (also known as qualitative palettes) are better. The basic rule is that you should use distinct hues when your values don’t have an inherent order or range. Note that this does not include Likert scales (“strongly agree, agree, neutral, disagree, strongly disagree”), because even though there are distinct categories, there is an order to the possible responses.\n",
    "\n",
    "Here are some examples of the qualitative hues available in the visualisation package **matplotlib**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "# remove-input\n",
    "cmaps = [\n",
    "    (\n",
    "        \"Perceptually Uniform Sequential\",\n",
    "        [\"viridis\", \"plasma\", \"inferno\", \"magma\", \"cividis\"],\n",
    "    ),\n",
    "    (\n",
    "        \"Sequential\",\n",
    "        [\n",
    "            \"Greys\",\n",
    "            \"Purples\",\n",
    "            \"Blues\",\n",
    "            \"Greens\",\n",
    "            \"Oranges\",\n",
    "            \"Reds\",\n",
    "            \"YlOrBr\",\n",
    "            \"YlOrRd\",\n",
    "            \"OrRd\",\n",
    "            \"PuRd\",\n",
    "            \"RdPu\",\n",
    "            \"BuPu\",\n",
    "            \"GnBu\",\n",
    "            \"PuBu\",\n",
    "            \"YlGnBu\",\n",
    "            \"PuBuGn\",\n",
    "            \"BuGn\",\n",
    "            \"YlGn\",\n",
    "        ],\n",
    "    ),\n",
    "    (\n",
    "        \"Sequential (2)\",\n",
    "        [\n",
    "            \"binary\",\n",
    "            \"gist_yarg\",\n",
    "            \"gist_gray\",\n",
    "            \"gray\",\n",
    "            \"bone\",\n",
    "            \"pink\",\n",
    "            \"spring\",\n",
    "            \"summer\",\n",
    "            \"autumn\",\n",
    "            \"winter\",\n",
    "            \"cool\",\n",
    "            \"Wistia\",\n",
    "            \"hot\",\n",
    "            \"afmhot\",\n",
    "            \"gist_heat\",\n",
    "            \"copper\",\n",
    "        ],\n",
    "    ),\n",
    "    (\n",
    "        \"Diverging\",\n",
    "        [\n",
    "            \"PiYG\",\n",
    "            \"PRGn\",\n",
    "            \"BrBG\",\n",
    "            \"PuOr\",\n",
    "            \"RdGy\",\n",
    "            \"RdBu\",\n",
    "            \"RdYlBu\",\n",
    "            \"RdYlGn\",\n",
    "            \"Spectral\",\n",
    "            \"coolwarm\",\n",
    "            \"bwr\",\n",
    "            \"seismic\",\n",
    "        ],\n",
    "    ),\n",
    "    (\"Cyclic\", [\"twilight\", \"twilight_shifted\", \"hsv\"]),\n",
    "    (\n",
    "        \"Qualitative\",\n",
    "        [\n",
    "            \"Pastel1\",\n",
    "            \"Pastel2\",\n",
    "            \"Paired\",\n",
    "            \"Accent\",\n",
    "            \"Dark2\",\n",
    "            \"Set1\",\n",
    "            \"Set2\",\n",
    "            \"Set3\",\n",
    "            \"tab10\",\n",
    "            \"tab20\",\n",
    "            \"tab20b\",\n",
    "            \"tab20c\",\n",
    "        ],\n",
    "    ),\n",
    "    (\n",
    "        \"Miscellaneous\",\n",
    "        [\n",
    "            \"flag\",\n",
    "            \"prism\",\n",
    "            \"ocean\",\n",
    "            \"gist_earth\",\n",
    "            \"terrain\",\n",
    "            \"gist_stern\",\n",
    "            \"gnuplot\",\n",
    "            \"gnuplot2\",\n",
    "            \"CMRmap\",\n",
    "            \"cubehelix\",\n",
    "            \"brg\",\n",
    "            \"gist_rainbow\",\n",
    "            \"rainbow\",\n",
    "            \"jet\",\n",
    "            \"turbo\",\n",
    "            \"nipy_spectral\",\n",
    "            \"gist_ncar\",\n",
    "        ],\n",
    "    ),\n",
    "]\n",
    "\n",
    "\n",
    "gradient = np.linspace(0, 1, 256)\n",
    "gradient = np.vstack((gradient, gradient))\n",
    "\n",
    "\n",
    "def plot_color_gradients(cmap_category, cmap_list):\n",
    "    # Create figure and adjust figure height to number of colormaps\n",
    "    nrows = len(cmap_list)\n",
    "    figh = 0.35 + 0.15 + (nrows + (nrows - 1) * 0.1) * 0.22\n",
    "    fig, axs = plt.subplots(nrows=nrows, figsize=(6.4, figh))\n",
    "    fig.subplots_adjust(top=1 - 0.35 / figh, bottom=0.15 / figh, left=0.2, right=0.99)\n",
    "\n",
    "    axs[0].set_title(cmap_category + \" colormaps\", fontsize=14)\n",
    "\n",
    "    for ax, name in zip(axs, cmap_list):\n",
    "        ax.imshow(gradient, aspect=\"auto\", cmap=plt.get_cmap(name))\n",
    "        ax.text(\n",
    "            -0.01,\n",
    "            0.5,\n",
    "            name,\n",
    "            va=\"center\",\n",
    "            ha=\"right\",\n",
    "            fontsize=10,\n",
    "            transform=ax.transAxes,\n",
    "        )\n",
    "\n",
    "    # Turn off *all* ticks & spines, not just the ones with colormaps.\n",
    "    for ax in axs:\n",
    "        ax.set_axis_off()\n",
    "\n",
    "\n",
    "for cmap_category, cmap_list in cmaps[5:6]:\n",
    "    plot_color_gradients(cmap_category, cmap_list)\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Continuous Colour Scales\n",
    "\n",
    "Continuously varying data need a sequential colour scale, but there are two types: *sequential* and *diverging*\n",
    "\n",
    "For data that vary from low to high, you can use a sequential colourmap. Best practice is to use a sequential colourmap that is perceptually uniform. The authors of the Python package **matplotlib** [developed](https://bids.github.io/colormap/) several perceptually uniform colourmaps that are now widely used, not just in Python, but in other languages and contexts too {cite:ps}`nunez2018optimizing`. These are the ones built-in to **matplotlib**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "# remove input\n",
    "for cmap_category, cmap_list in cmaps[:1]:\n",
    "    plot_color_gradients(cmap_category, cmap_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Do not use the JET colourmap: it is very much not perceptually uniform. If you do want a rainbow-like sequential and perceptually uniform colourmap, then turbo, [developed by Google](https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html), is as good a choice as you're going to find. You can find turbo within matplotlib."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "# remove-input\n",
    "def show_one_colourmap(cmap):\n",
    "    fig, ax = plt.subplots(figsize=(6.4, 0.3))\n",
    "    ax.text(\n",
    "        -0.01, 0.5, cmap, va=\"center\", ha=\"right\", fontsize=10, transform=ax.transAxes\n",
    "    )\n",
    "    ax.imshow(gradient, aspect=\"auto\", cmap=plt.get_cmap(cmap))\n",
    "    ax.set_axis_off()\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "show_one_colourmap(\"turbo\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sometimes a *diverging* colourmap will be more appropriate for your data. These are also called bipolar or double-ended color scales and, instead of just going from low to high, they tend to have a default middle value (often brighter) with values either side that are darker in different hues. Diverging color scales are often used to visualise negative and positive differences relative to zero, election results, or Likert scales (for example, “strongly agree, agree, neutral, disagree, strongly disagree”).\n",
    "\n",
    "These are the built-in ones in **matplotlib**:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "# remove input\n",
    "for cmap_category, cmap_list in cmaps[3:4]:\n",
    "    plot_color_gradients(cmap_category, cmap_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So how do you choose between a diverging or sequential colour scale? Divering colour scales work better when i) there’s a meaningful middle point, ii) there are extremes that you want to emphasise, iii) when differences are more of the story than levels, and iv) when you don't mind that people will have to put in a bit of extra effort to understand the chart relative to the typically more intuitive sequential colour scale."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, this book uses a colour-blind friendly qualitative scheme (you can find the list of colours in [this](https://github.com/aeturrell/coding-for-economists/blob/main/plot_style.txt) file)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "# remove input\n",
    "from matplotlib.colors import LinearSegmentedColormap\n",
    "\n",
    "colours = plt.rcParams[\"axes.prop_cycle\"].by_key()[\"color\"]\n",
    "cmap = LinearSegmentedColormap.from_list(\n",
    "    \"coding for economists: qualitative colourmap\", colours, N=len(colours)\n",
    ")\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(6.4, 0.3))\n",
    "ax.imshow(gradient, aspect=\"auto\", cmap=plt.get_cmap(cmap))\n",
    "ax.text(\n",
    "    -0.01,\n",
    "    0.5,\n",
    "    \"Coding for Economists\",\n",
    "    va=\"center\",\n",
    "    ha=\"right\",\n",
    "    fontsize=10,\n",
    "    transform=ax.transAxes,\n",
    ")\n",
    "ax.set_axis_off()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Libraries for data visualisation\n",
    "\n",
    "In the rest of this chapter, we'll take a look at making visualisations with several of these libraries. But first, let's introduce them.\n",
    "\n",
    "The most important and widely used data visualisation library in Python is [**matplotlib**](https://matplotlib.org/). It was used to make the first image of a black hole {cite}`akiyama2019first` and to image the first empirical evidence of gravitational waves {cite}`abbott2016observation`. **matplotlib** is an imperative visualisation library: you specify each part of what you want individually to build up an entire picture. It's perhaps the easiest to get started with and the most difficult to master. As well as making plots, it can also be used to make diagrams, animations, and 3D visualisations (which you should use sparingly, if at all).\n",
    "\n",
    "[**seaborn**](https://seaborn.pydata.org/index.html) is a popular declarative library that builds on **maplotlib** and works especially well with data that are in a *tidy* format (one row per observation, one column per variable). I tend to use a mixture of **seaborn** and **matplotlib** in my work.\n",
    "\n",
    "[**plotnine**](https://plotnine.readthedocs.io/en/latest/) is another declarative plotting library but, rather than having lots of different functions (eg 'boxplot', 'violinplot', 'scatterplot') as **seaborn** does, it adopts a *grammar of graphics* approach. What this means is that *all* visualisations begin with the same command, `ggplot`, and are combinations of layers that address different aspects of a plot, for example points or lines, scale, labels, and so on. It'll be clearer when we come to an example.\n",
    "\n",
    "[**altair**](https://altair-viz.github.io/) is yet another declarative plotting library for Python! It's most suited to interactive graphics on the web, and produces really beautiful charts. Under the hood, it calls a javascript library named **Vega-Lite** that's the sort of thing newspaper data visualisation units might use to make their infographics.\n",
    "\n",
    "**pandas** also has built-in plotting functions that you will have seen in the data analysis part of this book. They are of the form `df.plot.*` where `*` could be, for example, `scatter`. These are convenience functions for making a quick plot of your data and they actually use **matplotlib**; we won't see much of these here but you can find them in the data analysis chapter.\n",
    "\n",
    "We're going to start this chapter by finding out a little bit more about each of these data visualisation libraries before looking at some examples of how to make specific plots with all the main libraries. We'll end by looking at some more interesting and advanced plots.\n",
    "\n",
    "### Matplotlib\n",
    "\n",
    "For a more in-depth introduction to matplotlib, head over to [this tutorial](https://github.com/rougier/matplotlib-tutorial).\n",
    "\n",
    "Each of these libraries has their own default theme for how it shows plots. Personally, I'm not much of a fan of the **matplotlib** default so the first thing I do is to change it to something more aesthetically pleasing and that uses the kind of fonts you'll see in a paper compiled with latex."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You'll see we imported `matplotlib.pyplot as plt` above; this is the main part of **matplotlib** that we'll use in practice.\n",
    "\n",
    "There are actually two ways to use **matplotlib**: the 'pyplot API' and the 'object-oriented API'. The pyplot API (application programming interface) gives an experience that's much closer to Matlab and can be useful to make a quick chart. It's very simple to use, for example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot([1, 2, 3, 4], [1, 4, 9, 16])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{admonition} Tip\n",
    ":class: tip\n",
    "**Matplotlib** returns an object when used, eg `[<matplotlib.lines.Line2D...` above. To suppress this, end the command with a semi-colon, `;`.\n",
    "```\n",
    "\n",
    "However, the pyplot API is generally less-flexible and less-useful than the object-oriented API, and for the rest of this chapter we'll be using the object-oriented API.\n",
    "\n",
    "The object-oriented API is most often used by creating two objects: the figure and the axes. You should think of the figure object, `fig`, as the canvas on which you can put any number of charts. Each ax (short for 'axis') object is one chart within a figure. Of course, most of the time you're likely only to have one axis per figure, but in the cases when you don't it's a really useful setup. The plotting of elements such as lines, points, bars, and so, are controlled by the `ax` objects while the overall settings are controlled by `fig`.\n",
    "\n",
    "Let's see an example of a line chart using this object-oriented approach."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots()  # Create a figure containing a single axes.\n",
    "ax.scatter([1, 2, 3, 4, 5, 6], [1, 4, 2, 3, 1, 7], s=150, c=\"b\")\n",
    "# Plot some data on the axes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the above example, we used `ax.scatter` to get a scatter plot, `s=150` to set the area of the points, and `c='b'` to set the color. Many of these features will accept an array instead of a single value and will map them into the plot in the way you'd expect, for instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.scatter(\n",
    "    [1, 2, 3, 4, 5, 6],\n",
    "    [1, 4, 2, 3, 1, 7],\n",
    "    s=np.linspace(300, 2000, 6),\n",
    "    c=[\"b\", \"r\", \"g\", \"k\", \"cyan\", \"yellow\"],\n",
    "    edgecolors=\"k\",\n",
    "    alpha=0.5,\n",
    ");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we asked for a different colour for each point, an area that's increasing linearly, partly transparent points (default is `alpha=1`, which is a solid colour), and a black edge colour.\n",
    "\n",
    "You can probably begin to see how everything is going to be customisable. We've only seen aspects of the plot that are customisable through the `scatter` keyword so far though; let's now see an example that's a bit more real (and useful!) in which we'll want to add labels, a title, and more. We'll use the Midwest demographics dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv(\n",
    "    \"https://vincentarelbundock.github.io/Rdatasets/csv/ggplot2/midwest.csv\",\n",
    "    index_col=\"PID\",\n",
    ").drop(\"Unnamed: 0\", axis=1)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.scatter(df[\"area\"], df[\"poptotal\"], edgecolors=\"k\", alpha=0.6)\n",
    "ax.set_xlim(0, 0.1)\n",
    "ax.set_ylim(0, 1e6)\n",
    "ax.set_xlabel(\"Area\")\n",
    "ax.set_ylabel(\"Population\")\n",
    "ax.set_title(\"Area vs. Population\", loc=\"right\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Perhaps you can see from this already that **matplotlib** offers a ton of customisation features *and* that it can be quite verbose.\n",
    "\n",
    "Let's see another couple of customisations that are really useful: formatting axes. The y-axis has already been formatted because we used a specific style file at the top of the page (the line beginning `plt.style.use`) but, on the x-axis, we'll add a percentage suffix on the numbers plus some minor tick marks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib.ticker import AutoMinorLocator\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "ax.scatter(df[\"area\"], df[\"poptotal\"], edgecolors=\"k\", alpha=0.6)\n",
    "ax.set_xlim(0, 0.1)\n",
    "ax.set_ylim(0, 1e6)\n",
    "ax.set_xlabel(\"Area\")\n",
    "ax.set_ylabel(\"Population\")\n",
    "ax.set_title(\"Area vs. Population\", loc=\"right\")\n",
    "ax.xaxis.set_minor_locator(AutoMinorLocator(4))\n",
    "ax.xaxis.set_major_formatter(\"{x:.2f}%\")\n",
    "ax.ticklabel_format(style=\"sci\", scilimits=(-2, 2), axis=\"y\", useMathText=True);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `AutoMinorLocator(4)` inserts 4 minor tick marks between each major tick mark. The major formatter is `'{x:.2f}%'`, which says print 2 decimal places followed by a % sign. Finally, the `ax.ticklabel_format` option changes the computer science notation to use latex to print a prettier form of the one million signifier.\n",
    "\n",
    "Rather than go through all of the many, many options for customisation, the figure below (from the Matplotlib documentation) gives an overview of the options:\n",
    "\n",
    "![Anatomy of a matplotlib figure](https://matplotlib.org/_images/anatomy.png)\n",
    "\n",
    "Okay a quick overview of what the most important plot elements are:\n",
    "\n",
    "- Figure, or 'fig': the figure keeps track of all the child Axes that are on it and a smattering of 'special' artists (titles, figure legends, etc). A figure can contain any number of Axes, but will typically have at least one.\n",
    "- Axes, or 'ax': this is the plot, the region of the image that traces out the data. A given Figure can contain many Axes, but a given Axes object can only be in one Figure. The Axes contains two (or three in the case of 3D) Axis objects (Axes and Axis are different things!) that record the data limits (you can override these via the `axes.Axes.set_xlim()` and `axes.Axes.set_ylim()` methods). Each Axes object has a title (set via `set_title()`), an x-label (set via `set_xlabel()`), and a y-label set via `set_ylabel()`). When you add, say, a line chart to an Axes object it appears as a Line2D object associated that that axis and it is created by calling a method on an Axes object.\n",
    "- Axis: these are the number-line objects that control the limits of what the viewer can see. They also provide the means to access the ticks (the marks on the axis) and ticklabels (strings labeling the ticks). The location of the ticks is determined by a Locator object and the ticklabel strings are formatted by a Formatter. The combination of Locator and Formatter gives very fine control over the tick locations and labels.\n",
    "\n",
    "You can find a detailed blog post on the ins and outs [here](https://dev.to/skotaro/artist-in-matplotlib---something-i-wanted-to-know-before-spending-tremendous-hours-on-googling-how-tos--31oo). The object-oriented structure of **matplotlib** plots can be confusing at first, but, as we'll see, it pretty much fades into the background when you're making plots.\n",
    "\n",
    "While **matplotlib** is super-customisable, sometimes achieving what you want directly can be a bit verbose. Let's say we want to now differentiate these points with colour according to which state they belong to and add a legend that says which states have which colour. The easiest way to do this is by creating a for loop."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "colours = plt.get_cmap(\"Dark2\")(np.linspace(0, 1, len(df[\"state\"].unique())))\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "for i, state in enumerate(df[\"state\"].unique()):\n",
    "    xf = df.loc[df[\"state\"] == state]\n",
    "    ax.scatter(\n",
    "        xf[\"area\"],\n",
    "        xf[\"poptotal\"],\n",
    "        color=colours[i],\n",
    "        label=state,\n",
    "        s=100,\n",
    "        edgecolor=\"k\",\n",
    "        alpha=0.8,\n",
    "    )\n",
    "ax.set_xlim(0, 0.1)\n",
    "ax.set_ylim(0, 1e6)\n",
    "ax.set_xlabel(\"Area\")\n",
    "ax.set_ylabel(\"Population\")\n",
    "ax.set_title(\"Area vs. Population\", loc=\"center\")\n",
    "ax.xaxis.set_minor_locator(AutoMinorLocator(4))\n",
    "ax.xaxis.set_major_formatter(\"{x:.2f}%\")\n",
    "ax.ticklabel_format(style=\"sci\", scilimits=(-2, 2), axis=\"y\", useMathText=True)\n",
    "ax.legend(title=\"State\", loc=\"upper right\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Okay, so we managed to get what we wanted. We used a colormap to get 5 qualitatively different colours; there are also sequential colormaps for continuous (as opposed to discrete) variables. You can find out more about the colormaps available in base matplotlib [here](https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html). We also subsetted the dataframe and looped over it by state. Matplotlib doesn't always work so well with tidy data. As you saw in this example, we had to loop over the different states. Some of its defaults are a little more friendly when using data that is unstacked, eg with one state per column.\n",
    "\n",
    "However, this was quite verbose; when we come to **seaborn**, we'll see a much easier way of doing this. \n",
    "\n",
    "For now, let's see a couple of other plot types that are useful, beginning with contour plots:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x, y):\n",
    "    return np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)\n",
    "\n",
    "\n",
    "x = np.linspace(0, 5, 100)\n",
    "y = np.linspace(0, 5, 100)\n",
    "\n",
    "X, Y = np.meshgrid(x, y)\n",
    "Z = f(X, Y)\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "cf = ax.contourf(X, Y, Z, cmap=\"plasma\")\n",
    "ax.set_title(r\"$f(x,y) = \\sin^{10}(x) + \\cos(x)\\cos\\left(10 + y\\cdot x\\right)$\")\n",
    "cbar = fig.colorbar(cf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This demonstrates creating a heatmap (or contour plot) with a colour bar legend and a title that's rendered with latex. The heatmap uses a perceptually uniform distribution that makes equal changes look equal; **matplotlib** has a few of these. If you need more colours, check out the packages [**colorcet**](https://colorcet.holoviz.org/) and [**palettable**](https://jiffyclub.github.io/palettable/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can do some really quite amazing things using **matplotlib**'s build what you want philosophy. For instance, you can [build a timeline](https://matplotlib.org/3.1.1/gallery/lines_bars_and_markers/timeline.html#sphx-glr-gallery-lines-bars-and-markers-timeline-py), make [charts with polar axes](https://matplotlib.org/3.1.1/gallery/pie_and_polar_charts/polar_bar.html#sphx-glr-gallery-pie-and-polar-charts-polar-bar-py), and create [XKCD style plots](https://matplotlib.org/3.1.1/gallery/showcase/xkcd.html#sphx-glr-gallery-showcase-xkcd-py).\n",
    "\n",
    "One basic bit of functionality that you might need is to put more than one type of information on a single plot. Using the object-oriented API, this is as simple as calling another method on an `ax` that you've already created. In the example below, we'll call `ax.hist` followed by `ax.plot` to get a the theoretical curve for a normal distribution (aka Gaussian) overlaid on a kernel density estimate based on many draws from the relevant distribution using `numpy`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rand_draws = np.random.randn(5000)\n",
    "grid_x = np.linspace(-5, 5, 1000)\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "ax.hist(rand_draws, bins=50, density=True)\n",
    "ax.plot(grid_x, 1 / np.sqrt(2 * np.pi) * np.exp(-(grid_x ** 2) / 2), linewidth=4);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another important class of plots that we should look at are line charts, especially time series. Let's grab the real GDP time series for the UK and US and look at the year-on-year quarterly growth rates (the first few entries will be `NaN` because we switched to growth space)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas_datareader.data as web\n",
    "\n",
    "ts_start_date = pd.to_datetime(\"1999-01-01\")\n",
    "\n",
    "df = pd.concat(\n",
    "    [\n",
    "        web.DataReader(\"ticker=RGDP\" + x, \"econdb\", start=ts_start_date)\n",
    "        for x in [\"US\", \"UK\"]\n",
    "    ],\n",
    "    axis=1,\n",
    ")\n",
    "df.columns = [\"US\", \"UK\"]\n",
    "df.index.name = \"Date\"\n",
    "df = 100 * df.pct_change(4)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Okay, now the quick way to plot this would be to simply call `df.plot()`. That works fine and doesn't look too bad: it recognises that it's dealing with a datetime and plots sensible tick labels on the x-axis, and it produces a legend for each of the two time series. But let's say we want to make that plot quickly *and* do some fine-tuning with **matplotlib**: how can we? The answer is to create a `fig` and an `ax` and then to ask our dataframe to use the `ax` we already created to plot the data. We can then carry on using the `ax` object in any way we like."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots()\n",
    "df.plot(ax=ax)\n",
    "ax.set_title(\"Real GDP growth, %\", loc=\"right\")\n",
    "ax.spines[\"right\"].set_visible(True)\n",
    "ax.spines[\"left\"].set_visible(False)\n",
    "ax.spines[\"top\"].set_visible(False)\n",
    "ax.yaxis.tick_right()\n",
    "# Put tick marks and tick labels on right-hand side"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could also have created this chart by looping through the different countries just as we looped through the difference states in the previous example.\n",
    "\n",
    "Let's also see what happens when we use different limits:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots()\n",
    "df.plot(ax=ax)\n",
    "ax.set_title(\"Real GDP growth, %\", loc=\"right\")\n",
    "ax.spines[\"right\"].set_visible(True)\n",
    "ax.spines[\"left\"].set_visible(False)\n",
    "ax.spines[\"top\"].set_visible(False)\n",
    "ax.yaxis.tick_right()  # Put tick marks and tick labels on right-hand side\n",
    "ax.set_xlim(pd.to_datetime(\"2018-01-01\"), pd.to_datetime(\"2021-01-01\"));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the plot dynamically responded to the shorter time period by putting more details in, here of quarters. You can specify exactly what you want with the tick label formatters that cater to datetimes, but the defaults are pretty well-behaved.\n",
    "\n",
    "Sometimes, you want to show different lines in different panels (or facets, as they are also known). You can do that with **matplotlib** too, though once again it's a build-your-own affair.\n",
    "\n",
    "Let's put these two time series in their own facets to see how it works."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 2)\n",
    "for i, ax in enumerate(axes):\n",
    "    ax.plot(df.index, df.iloc[:, i])\n",
    "    ax.set_title(df.columns[i], loc=\"left\")\n",
    "    ax.yaxis.tick_right()\n",
    "    ax.spines[\"right\"].set_visible(True)\n",
    "    ax.spines[\"left\"].set_visible(False)\n",
    "    ax.spines[\"top\"].set_visible(False)\n",
    "    ax.set_ylim(df.min().min(), df.max().max())\n",
    "fig.suptitle(\"Real GDP growth, %\")\n",
    "plt.tight_layout();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quite a few things are happening here. The first is that we asked for more rows and columns from matplotlib using `plt.subplots(nrows, ncolumns)` and instead of returning a single `ax` it returned a list of axes (here of length 2). We then iterated over those axes using enumerate, which gives an integer `i` and an `ax` from the list, and plotted a column on each axis by subsetting the dataframe one column at a time using `df.iloc[:, i]`. Remember that the first position in `iloc` is for the index values (ie the rows), while the second is for the columns. Again, this was quite a lot of code to get a relatively simple chart done! And that's exactly why we're now going to take a look at the declarative library **seaborn** that wraps **matplotlib**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Seaborn\n",
    "\n",
    "**seaborn** provides a high-level interface for quickly drawing standard charts. It is based on **matplotlib*, which is great because it means you can always tinker if you need to. And it plays very nicely with **pandas** dataframes too, so it can easily fit into your workflow.\n",
    "\n",
    "Here's an overview of the API (application programming interface; basically the commands we can feed the library) for **seaborn**. (NB: this figure excludes the facet option, which combines multiple versions of the below plots.)\n",
    "\n",
    "![seaborn api diagram](https://seaborn.pydata.org/_images/function_overview_8_0.png)\n",
    "\n",
    "Let's see the chart we were just trying to make but rendered in **seaborn**. The first difference is that **seaborn** expects *tidy data*, a concept from the Data part of this book. So first we must transform our data into tidy format.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tidy_df = df.stack().reset_index()\n",
    "tidy_df.columns = [\"Date\", \"Country\", \"Real GDP growth, %\"]\n",
    "tidy_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Okay now let's use seaborn to do the plotting. As with **matplotlib**, the way to get different charts in **seaborn** is a matter of knowing the API names. Here, we'll use `relplot`, which is for facets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import seaborn as sns\n",
    "\n",
    "sns.relplot(\n",
    "    data=tidy_df,\n",
    "    x=\"Date\",\n",
    "    y=\"Real GDP growth, %\",\n",
    "    hue=\"Country\",\n",
    "    col=\"Country\",\n",
    "    kind=\"line\",\n",
    "    legend=False,\n",
    ");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this case, we got a very similar plot with less effort--and what we do is more generalisable too. As long as we have data in a tidy format, we can specify the columns to seaborn and let it put them in different facets. And facets aren't the only difference it can provide either: you can specify the hue such that it depends on another categorical variable (rather than, as in this case, changing with each facet). Here's an example changing the hue to reflect an extra type of information, region:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Download RGDP for Canada and France:\n",
    "extra_df = pd.concat(\n",
    "    [\n",
    "        web.DataReader(\"ticker=RGDP\" + x, \"econdb\", start=ts_start_date)\n",
    "        for x in [\"CA\", \"FR\"]\n",
    "    ],\n",
    "    axis=1,\n",
    ")\n",
    "extra_df.columns = [\"CA\", \"FR\"]\n",
    "extra_df.index.name = \"Date\"\n",
    "extra_df = (100 * extra_df.pct_change(4)).stack().reset_index()\n",
    "extra_df.columns = [\"Date\", \"Country\", \"Real GDP growth, %\"]\n",
    "# Add the new data to the tidy dataframe\n",
    "tidy_df = pd.concat([tidy_df, extra_df], axis=0)\n",
    "continent_dict = {\n",
    "    \"CA\": \"North America\",\n",
    "    \"US\": \"North America\",\n",
    "    \"UK\": \"Europe\",\n",
    "    \"FR\": \"Europe\",\n",
    "}\n",
    "tidy_df[\"Region\"] = tidy_df[\"Country\"].map(continent_dict)\n",
    "tidy_df.sample(6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.relplot(\n",
    "    data=tidy_df,\n",
    "    x=\"Date\",\n",
    "    y=\"Real GDP growth, %\",\n",
    "    hue=\"Region\",\n",
    "    col=\"Country\",\n",
    "    col_wrap=2,\n",
    "    kind=\"line\",\n",
    "    legend=False,\n",
    ");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we now have two colours: one for each region.\n",
    "\n",
    "Let's see some of the other useful shortcuts that **seaborn** provides over the top of **matplotlib**. First, correlation heatmaps:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generate data and create corr mat\n",
    "d = pd.DataFrame(\n",
    "    data=np.random.normal(size=(100, 6)), columns=[\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"]\n",
    ")\n",
    "corr = d.corr()\n",
    "\n",
    "# Generate a mask to cover the upper triangle\n",
    "mask = np.triu(np.ones_like(corr, dtype=bool))\n",
    "\n",
    "# Draw heatmap\n",
    "sns.heatmap(\n",
    "    corr,\n",
    "    mask=mask,\n",
    "    cmap=\"magma\",\n",
    "    vmin=-0.4,\n",
    "    vmax=0.4,\n",
    "    center=0,\n",
    "    square=True,\n",
    "    linewidths=0.5,\n",
    "    cbar_kws={\"shrink\": 0.5},\n",
    ");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**seaborn** also offers convenience functions for (continuous) heatmaps, kernel density estimates, and marginal plots:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = sns.load_dataset(\"penguins\")\n",
    "\n",
    "g = sns.JointGrid(data=df, x=\"body_mass_g\", y=\"bill_depth_mm\", space=0)\n",
    "g.plot_joint(\n",
    "    sns.kdeplot,\n",
    "    fill=True,\n",
    "    clip=((2200, 6800), (10, 25)),\n",
    "    thresh=0,\n",
    "    levels=100,\n",
    "    cmap=\"inferno\",\n",
    ")\n",
    "g.plot_marginals(sns.histplot, color=\"#03051A\", alpha=1, bins=25);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And plotting simple linear models according to a category:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g = sns.lmplot(\n",
    "    data=df,\n",
    "    x=\"bill_length_mm\",\n",
    "    y=\"bill_depth_mm\",\n",
    "    hue=\"species\",\n",
    "    legend=False,\n",
    "    line_kws={\"lw\": 1.0},\n",
    "    height=5,\n",
    "    aspect=1.75,\n",
    ")\n",
    "plt.legend(loc=\"lower left\", frameon=True);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And, finally, violin plots:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tips = sns.load_dataset(\"tips\")\n",
    "sns.violinplot(\n",
    "    data=tips,\n",
    "    x=\"day\",\n",
    "    y=\"total_bill\",\n",
    "    hue=\"smoker\",\n",
    "    split=True,\n",
    "    inner=\"quart\",\n",
    "    linewidth=1,\n",
    ");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plotnine\n",
    "\n",
    "[**plotnine**](https://plotnine.readthedocs.io/en/stable/) is, like **seaborn**, a declarative library. Unlike **seaborn**, it adopts a 'grammar of graphics' approach inspired by the book 'The Grammar of Graphics' by Leland Wilkinson. **plotnine** is heavily inspired by the API of the popular **ggplot2** plotting package in the statistical programming language R. The point behind the grammar of graphics approach is that users can compose plots by explicitly mapping data to the various elements that make up the plot. It is a particularly effective approach for a whole slew of standard plots created from tidy data.\n",
    "\n",
    "Before playing with **plotnine** though, I found that my preferred settings for **maplotlib** made the (very sensible) defaults for **plotnine** go awry, particularly for facet plots, so I'm going to switch back to the matplotlib defaults:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib as mpl\n",
    "\n",
    "mpl.rcParams.update(mpl.rcParamsDefault)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take a look at how to do a simple scatter plot, like we saw for **matplotlib**, in **plotnine**. We'll use the *mtcars* dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import ggplot, geom_point, aes\n",
    "from plotnine.data import mtcars\n",
    "\n",
    "(ggplot(mtcars, aes(\"wt\", \"mpg\")) + geom_point())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here, `ggplot` is the organising framework for creating a plot and `mtcars` is a dataframe with the data in that we'd like to plot. `aes` stands for aesthetic mapping and it tells **plotnine** which columns of the dataframe to treat as the x and y axis (in that order). Finally, `geom_point` tells **plotnine** to add scatter points to the plot.\n",
    "\n",
    "If we want to add colour, we pass a colour keyword argument to `aes` like so (with 'factor' meaning treat the variable like it's a categorical):\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(ggplot(mtcars, aes(\"wt\", \"mpg\", color=\"factor(gear)\")) + geom_point())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the nice aspects of the grammar of graphics approach, perhaps its best feature, is that switching to other types of 'geom' (aka chart type) is as easy as calling the same code but with a different 'geom' switched in. Note that, because we only imported one element at a time from **plotnine** we do need to explicitly import any other 'geoms' that we'd like to use, as in the next example below. But we *could* have just imported everything from **plotnine** instead using `from plotnine import *`.\n",
    "\n",
    "The next example shows how easy it is to switch between 'geoms'.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import geom_smooth\n",
    "\n",
    "(ggplot(mtcars, aes(\"wt\", \"mpg\")) + geom_smooth())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Furthermore, we can add *multiple* geoms to the same chart by layering them within the same call to the `ggplot` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(ggplot(mtcars, aes(\"wt\", \"mpg\")) + geom_smooth(color=\"blue\") + geom_point())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just like **seaborn** and **matplotlib**, we can create facet plots too--but this time they're just a variation on the same underlying call to `ggplot`. Let's see that same example of GDP by country rendered with **plotnine**: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import geom_line, facet_wrap, theme, element_text\n",
    "\n",
    "(\n",
    "    ggplot(tidy_df, aes(x=\"Date\", y=\"Real GDP growth, %\", color=\"factor(Region)\"))\n",
    "    + geom_line()\n",
    "    + facet_wrap(\"Country\", nrow=2)\n",
    "    + theme(axis_text_x=element_text(rotation=90))\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**plotnine** can do many of the same types of charts as **seaborn**; let's see some similar examples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import geom_violin, scale_fill_manual\n",
    "\n",
    "(\n",
    "    ggplot(tips, aes(\"day\", \"total_bill\", fill=\"smoker\"))\n",
    "    + geom_violin(tips)\n",
    "    + scale_fill_manual(values=[\"dodgerblue\", \"darkorange\"])\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import labs\n",
    "\n",
    "(\n",
    "    ggplot(df, aes(x=\"bill_length_mm\", y=\"bill_depth_mm\", color=\"factor(species)\"))\n",
    "    + geom_point()\n",
    "    + geom_smooth(method=\"lm\")\n",
    "    + labs(x=\"Bill length (mm)\", y=\"Bill depth (mm)\")\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, an example of great practical use during exploratory analysis, the kernel density plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import geom_density\n",
    "from plotnine.data import mpg\n",
    "\n",
    "(ggplot(mpg, aes(x=\"cty\", color=\"drv\", fill=\"drv\")) + geom_density(alpha=0.1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Altair\n",
    "\n",
    "[**Altair**](https://altair-viz.github.io/) is similar to **plotnine** and **seaborn**, in that it's declarative, but it's very dissimilar in its look and feel. It isn't based on **matplotlib** and it's really more suited to displaying graphics on the web than anything else. Under the hood, it uses powerful javascript visualisation libraries. Because you're unlikely to use it in a paper, we won't give it quite the same amount of time or consideration as the other libraries.\n",
    "\n",
    "A note of caution about **altair**: you shouldn't use it for very large datasets (see the guidance [here](https://altair-viz.github.io/user_guide/faq.html#altair-faq-large-notebook)--there is a way around it) and, last time I tried, charts couldn't easily be exported to a PDF friendly format. It's really geared around web visualisation, and very beautiful web vis at that!\n",
    "\n",
    "The first thing to understand about **altair** is that, in a similar way to **plotnine**, the creation of a chart is always the same and it's only the specification of the type of chart that happens differently. Here's a by now familiar scatterplot example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import altair as alt\n",
    "from vega_datasets import data\n",
    "\n",
    "cars = data.cars()\n",
    "\n",
    "alt.Chart(cars).mark_circle(size=60).encode(\n",
    "    x=\"Horsepower\", y=\"Miles_per_Gallon\", color=\"Origin\", tooltip=[\"Name\"]\n",
    ").interactive()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There's a lot to unpack here! The very basic elements of the chart come from calling\n",
    "\n",
    "```python\n",
    "alt.Chart(cars).mark_circle().encode(\n",
    "    x='Horsepower',\n",
    "    y='Miles_per_Gallon',\n",
    "    color='Origin',\n",
    ")\n",
    "```\n",
    "\n",
    "as this asks for a chart using the (tidy) mtcars dataframe with circles that are coloured by the variable 'gear' treated as an ordinal (aka a category or factor) using `color=gear:O`. I had to specify that this should be  There are some extras. I added bigger circles and interactivity so that you can i) move the chart around as you like and ii) if you hover over any of the points, you will see the 'name' variable for that entry displayed. The latter is achieved via the tooltip keyword argument.\n",
    "\n",
    "As with ggplot, we can add multiple elements to a single plot. In this case, **altair** uses a clever property of Python called overloading to 'overload' the addition operator so that chart1 + chart2 creates a combined chart of both:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = (\n",
    "    alt.Chart(cars)\n",
    "    .mark_point()\n",
    "    .encode(x=\"Horsepower\", y=\"Miles_per_Gallon\", tooltip=[\"Name\"])\n",
    ")\n",
    "\n",
    "line = points.transform_loess(\"Horsepower\", \"Miles_per_Gallon\").mark_line(color=\"black\")\n",
    "\n",
    "(points + line).interactive()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "They have also overloaded other symbols so that charts can be shown in relative positions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = (\n",
    "    alt.Chart(cars)\n",
    "    .mark_circle(size=60)\n",
    "    .encode(x=\"Horsepower\", y=\"Miles_per_Gallon\", color=\"Origin\", tooltip=[\"Name\"])\n",
    ")\n",
    "\n",
    "hist = (\n",
    "    alt.Chart(cars)\n",
    "    .mark_bar()\n",
    "    .encode(\n",
    "        x=\"count()\",\n",
    "        y=alt.Y(\"Miles_per_Gallon:Q\", bin=alt.Bin(maxbins=30)),\n",
    "        color=\"Origin:N\",\n",
    "    )\n",
    ")\n",
    "\n",
    "points | hist"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that `:N` tells **altair** to treat the variable preceeding the colon as a nominal value, ditto `:Q` for quantitative values. Most of the time **altair** will guess what you need, but it's easy to specify it if you need to. More complex combinations of chart arrangements may be achieved with the `alt.hconcat` and `alt.vconcat` functions.\n",
    "\n",
    "Let's now return to some of our favourite examples and see how they look in **altair**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "alt.Chart(tidy_df).mark_line().encode(\n",
    "    x=\"Date:T\",\n",
    "    y=\"Real GDP growth, %\",\n",
    "    color=\"Region:N\",\n",
    "    facet=alt.Facet(\"Country:N\", columns=2),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "alt.Chart(mpg).transform_density(\n",
    "    density=\"cty\", as_=[\"cty\", \"density\"], groupby=[\"drv\"]\n",
    ").mark_area(fillOpacity=0.8).encode(\n",
    "    x=\"cty:Q\",\n",
    "    y=\"density:Q\",\n",
    "    color=\"drv:N\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chart = (\n",
    "    alt.Chart(df)\n",
    "    .mark_point()\n",
    "    .encode(x=\"bill_length_mm\", y=\"bill_depth_mm\", color=\"species:N\")\n",
    ")\n",
    "\n",
    "(\n",
    "    chart\n",
    "    + chart.transform_regression(\"bill_length_mm\", \"bill_depth_mm\", groupby=[\"species\"])\n",
    "    .mark_line()\n",
    "    .interactive()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "alt.Chart(tips).transform_density(\n",
    "    \"total_bill\", as_=[\"total_bill\", \"density\"], groupby=[\"day\"]\n",
    ").mark_area(orient=\"horizontal\").encode(\n",
    "    y=\"total_bill:Q\",\n",
    "    color=\"day:N\",\n",
    "    x=alt.X(\n",
    "        \"density:Q\",\n",
    "        stack=\"center\",\n",
    "        impute=None,\n",
    "        title=None,\n",
    "        axis=alt.Axis(labels=False, values=[0], grid=False, ticks=True),\n",
    "    ),\n",
    "    column=alt.Column(\n",
    "        \"day:N\",\n",
    "        header=alt.Header(\n",
    "            titleOrient=\"bottom\",\n",
    "            labelOrient=\"bottom\",\n",
    "            labelPadding=0,\n",
    "        ),\n",
    "    ),\n",
    ").properties(\n",
    "    width=100\n",
    ").configure_facet(\n",
    "    spacing=0\n",
    ").configure_view(\n",
    "    stroke=None\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I'm not an expert at **altair**, and there are many more (and, frankly, better) examples in their [gallery](https://altair-viz.github.io/gallery/index.html). It's a really rich tool though and, in expert hands, could be used to create the kind of visualisations that you might see in newspaper.\n",
    "\n",
    "But let's just see what someone who *does* know what they're doing can achieve using it using one of their examples (and how poorly South London is served by tube lines!):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "boroughs = alt.topo_feature(data.londonBoroughs.url, \"boroughs\")\n",
    "tubelines = alt.topo_feature(data.londonTubeLines.url, \"line\")\n",
    "centroids = data.londonCentroids.url\n",
    "\n",
    "background = (\n",
    "    alt.Chart(boroughs)\n",
    "    .mark_geoshape(stroke=\"white\", strokeWidth=2)\n",
    "    .encode(\n",
    "        color=alt.value(\"#eee\"),\n",
    "    )\n",
    "    .properties(width=700, height=500)\n",
    ")\n",
    "\n",
    "labels = (\n",
    "    alt.Chart(centroids)\n",
    "    .mark_text()\n",
    "    .encode(\n",
    "        longitude=\"cx:Q\",\n",
    "        latitude=\"cy:Q\",\n",
    "        text=\"bLabel:N\",\n",
    "        size=alt.value(8),\n",
    "        opacity=alt.value(0.6),\n",
    "    )\n",
    "    .transform_calculate(\n",
    "        \"bLabel\",\n",
    "        \"indexof (datum.name,' ') > 0  ? substring(datum.name,0,indexof(datum.name, ' ')) : datum.name\",\n",
    "    )\n",
    ")\n",
    "\n",
    "line_scale = alt.Scale(\n",
    "    domain=[\n",
    "        \"Bakerloo\",\n",
    "        \"Central\",\n",
    "        \"Circle\",\n",
    "        \"District\",\n",
    "        \"DLR\",\n",
    "        \"Hammersmith & City\",\n",
    "        \"Jubilee\",\n",
    "        \"Metropolitan\",\n",
    "        \"Northern\",\n",
    "        \"Piccadilly\",\n",
    "        \"Victoria\",\n",
    "        \"Waterloo & City\",\n",
    "    ],\n",
    "    range=[\n",
    "        \"rgb(137,78,36)\",\n",
    "        \"rgb(220,36,30)\",\n",
    "        \"rgb(255,206,0)\",\n",
    "        \"rgb(1,114,41)\",\n",
    "        \"rgb(0,175,173)\",\n",
    "        \"rgb(215,153,175)\",\n",
    "        \"rgb(106,114,120)\",\n",
    "        \"rgb(114,17,84)\",\n",
    "        \"rgb(0,0,0)\",\n",
    "        \"rgb(0,24,168)\",\n",
    "        \"rgb(0,160,226)\",\n",
    "        \"rgb(106,187,170)\",\n",
    "    ],\n",
    ")\n",
    "\n",
    "lines = (\n",
    "    alt.Chart(tubelines)\n",
    "    .mark_geoshape(filled=False, strokeWidth=2)\n",
    "    .encode(\n",
    "        alt.Color(\n",
    "            \"id:N\", legend=alt.Legend(title=None, orient=\"bottom-right\", offset=0)\n",
    "        )\n",
    "    )\n",
    ")\n",
    "\n",
    "background + labels + lines"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Other useful data visualisation tools\n",
    "\n",
    "#### Honourable mentions\n",
    "\n",
    "There are tons of data visualisation libraries in Python and it didn't seem sensible to feature them all in great detail, but here are a few more that may be worth looking into depending on what you need.\n",
    "\n",
    "[**proplot**](https://proplot.readthedocs.io/en/latest/index.html) aims to be \"A lightweight matplotlib wrapper for making beautiful, publication-quality graphics\", though the style is more similar to how people might make plots in the hard sciences rather than the social sciences. The point of this library is to take some of the verbosity out of **matplotlib**:\n",
    "\n",
    "> ProPlot’s core mission is to provide a smoother plotting experience for matplotlib’s heaviest users. We accomplish this by expanding upon the object-oriented matplotlib API. ProPlot makes changes that would be hard to justify or difficult to incorporate into matplotlib itself, owing to differing design choices and backwards compatibility considerations.\n",
    "\n",
    "There are a lot of nice features here that aim to make life easier (or at least a lot more concise) when you are mostly using **matplotlib**, you can check out the **proplot** documentation to find out more.  **proplot**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As to other libraries, here's a quick run down:\n",
    "\n",
    "- if you're using very big data in machine learning models, it might be worth looking at Facebook's [**hiplot**](https://github.com/facebookresearch/hiplot)\n",
    "- [Seaborn image](https://seaborn-image.readthedocs.io/en/latest/) does for image data what **seaborn** does for numerical and categorical data\n",
    "- [Lit](https://pair-code.github.io/lit/) provides an open-source platform for visualization and understanding of NLP models (very impressive)\n",
    "- [Wordcloud](https://github.com/amueller/word_cloud) does exactly what you'd expect (but use word clouds *very* sparingly!)\n",
    "- [plotly express](https://plotly.com/python/plotly-express/) is another declarative-leaning library that's suited to web apps and dashboards.\n",
    "- [VisPy](http://vispy.org) for very large datasets plotted with WebGL and GPU acceleration.\n",
    "- [PyQtGraph](http://www.pyqtgraph.org/), a pure-Python graphics library for PyQt5/PySide2 and intended for use in (heavy) mathematics / scientific / engineering applications (not very user friendly).\n",
    "- [bokeh](https://docs.bokeh.org/en/latest/index.html#) offers interactive web plotting in Python.\n",
    "- [HoloViews](https://holoviews.org/), a library designed to make data analysis and visualization seamless and simple with very concise commands (builds on bokeh and matplotlib).\n",
    "- [YellowBrick](https://www.scikit-yb.org/en/latest/) for visualisations of machine learning models and metrics.\n",
    "- [facets](https://pair-code.github.io/facets/) for quickly visualising machine learning features (aka regressors). Also useful for exploratory data analysis.\n",
    "- [chartify](https://github.com/spotify/chartify), Spotify's quick plotting library for data scientists.\n",
    "- [scikit-plot](https://github.com/reiinakano/scikit-plot) offers plotting tools designed around Python's wildy popular scikit-learn machine learning library.\n",
    "- [themepy](https://github.com/petermckeeverPerform/themepy) is an open source theme selector / creator and aesthetic manager for Matplotlib.\n",
    "- [scienceplots](https://github.com/garrettj403/SciencePlots) provides scientific plotting styles--some associated with specific journals--for Matplotlib.\n",
    "- [colour](https://www.colour-science.org/) provides professional level colour tools for Python.\n",
    "- [palettable](https://jiffyclub.github.io/palettable/) has extra colour palettes that work well with Matplotlib.\n",
    "- [colorcet](https://colorcet.holoviz.org/) is a collection of perceptually uniform colourmaps.\n",
    "- [missingno](https://github.com/ResidentMario/missingno) for visualization of missing data.\n",
    "- [bashplotlib](https://github.com/glamp/bashplotlib), for when you want to make visualisations directly from the command line (I don't imagine this will be very often, but always good to know the option is there!)\n",
    "\n",
    "You can see an overview of all Python plotting tools at [PyViz](https://pyviz.org/tools.html).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Review\n",
    "\n",
    "If you know:\n",
    "\n",
    "- ✅ a little bit about how to use data visualisation;\n",
    "- ✅ what some of the most popular libraries for data vis are; and\n",
    "- ✅ how to make simple plots in the most popular data vis libraries,\n",
    "\n",
    "then you are well on your way to being a whizz with data vis!"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "interpreter": {
   "hash": "e82e9306cec5f3813db66e68c531c869f324183486c55e9589deb7f101067873"
  },
  "kernelspec": {
   "display_name": "Python 3.8.12 64-bit ('codeforecon': conda)",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
